/*
 * yocef - Lightweight IoC Event Framework
 *
 * Copyright (c) 2009, Yves Cuillerdier
 * All rights reserved.
 * 
 * This software is licensed under the new BSD License:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the author nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * @version 1.0.2
 */

package yocef.client;

import java.util.Map;
import java.util.TreeMap;

/**
 * Action for an <tt>{Event,Object}</tt> couple.
 * <p>
 * This class represents an <tt>EventAction</tt> registered for an
 * <tt>{Event,Object}</tt> couple.
 * <p>
 * An <tt>EventAction</tt> could be performed in a {@link EventContext}
 */
public final class EventBind {

	/**
	 * Map that could be used to save/restore miscellaneous data. The main usage
	 * is to store objects build by the EventGlue to be later unregistered
	 */
	private final Map<String, Object> bindData = new TreeMap<String, Object>();

	/**
	 * The EventBindSet to which it belongs
	 */
	private final EventBindSet parent;

	/**
	 * The action to be performed in response to the event occurring on the
	 * source element.
	 */
	private final EventAction action;

	/**
	 * The context for the
	 * {@link EventAction#handle(Event, Object, EventContext, EventParams)} when
	 * fired
	 */
	private EventContext context;

	/** Package constructor */
	EventBind(final EventBindSet p, final EventAction a) {
		parent = p;
		action = a;
	}

	/**
	 * Declare a context for the action when fired. If a context is already
	 * defined, it will be replaced silently.
	 * 
	 * @param ctx
	 *            an EventContext to be provided to the
	 *            {@link EventAction#handle(Event, Object, EventContext, EventParams)}
	 *            when called. Could be null to "erase" a previous context.
	 * @return this EventBind for easy chaining
	 * @see EventContext
	 */
	public EventBind using(final EventContext ctx) {
		context = ctx;
		return this;
	}

	/**
	 * Calling this method indicates that we are ready to process the event on
	 * the source object.
	 * <p>
	 * the source element is "physically" bind with the event type. From that
	 * moment, event could be received and handle.
	 * <p>
	 * The way the event is binded with the element is defined by an
	 * {@link EventGlue} instance associated with the source class (or a parent
	 * class of the source).
	 * <p>
	 * The link between the element and the event is done in the
	 * {@link EventGlue#accept(EventBind)} method.
	 * 
	 * @see EventGlue
	 */
	public void now() {
		/*
		 * Try to find a glue: Start from the source element class up through
		 * superclass until a glue registration is found and accept it.
		 */
		Class<?> classToBind = getSource().getClass();
		while (classToBind != null) {
			final EventGlue glue = EventGlue.get(classToBind);
			if (glue != null && glue.accept(this)) {
				return;
			}
			classToBind = classToBind.getSuperclass();
		}
		throw new RuntimeException("No EventBinder found for " //$NON-NLS-1$
				+ getSource().getClass());
	}

	/**
	 * Get the Event associated with this EventBind.
	 * 
	 * @return the event. Not null
	 */
	public Event getEvent() {
		return parent.getEvent();
	}

	/**
	 * Get the source Object associated with this EventBind.
	 * 
	 * @return the source object. Not null
	 */
	public Object getSource() {
		return parent.getSource();
	}

	/**
	 * Get the Action associated with this EventBind.
	 * 
	 * @return the action. Normally not null, except if we forgot to set it with
	 *         the {@link EventBindSet#perform(EventAction)} method
	 */
	public EventAction getAction() {
		return action;
	}

	/**
	 * Get the context associated with this EventBind.
	 * 
	 * @return the context. Could be null
	 */
	public EventContext getContext() {
		return context;
	}

	/**
	 * Fire the actions.
	 * <p>
	 * package visibility
	 * 
	 * @param params
	 *            the parameters of the event
	 * @return <tt>true</tt> means "please go on" while <tt>false</tt> meant for
	 *         "stop processing".
	 */
	boolean fire(final EventParams params) {
		return action.handle(getEvent(), getSource(), context, params);
	}

	/**
	 * Save a data object in this EventBing. This method is typically used to
	 * store handler reference in {@link EventGlue} for native events. The
	 * instance could be retrieved by {@link EventBind#getData(String)}.
	 * 
	 * @param key
	 *            the key for the object
	 * @param object
	 *            the object to save
	 */
	public void addData(final String key, final Object object) {
		bindData.put(key, object);
	}

	/**
	 * Get and <b>remove</b> the object associated with the key.
	 * 
	 * @param key
	 *            the object key
	 * @return the object associated with the key
	 */
	public Object getData(final String key) {
		return bindData.remove(key);
	}

	// //////////////////////////////////////////////////////////////
	// Housekeeping
	// ////////////////////////////////////////////////////////////

	/**
	 * The Source will no more respond to the event. Perform housekeeping
	 * actions.
	 * <p>
	 * For internal use only!
	 */
	void free() {
		// Unregister listeners
		Class<?> classToUnbind = getSource().getClass();
		while (classToUnbind != null) {
			final EventGlue glue = EventGlue.get(classToUnbind);
			if (glue != null && glue.release(this)) {
				return;
			}
			classToUnbind = classToUnbind.getSuperclass();
		}
	}
}
